﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;

namespace Editor
{
    public class DataImporter : MonoBehaviour
    {
#if UNITY_EDITOR
        /// <summary>
        /// 生成数据类目录
        /// </summary>
        [SerializeField]
        private string _GenerateDataClassDir;

        /// <summary>
        /// 数据头
        /// </summary>
        private class DataHeader
        {
            /// <summary>
            /// 列数
            /// </summary>
            public int Column;
            
            /// <summary>
            /// 数据头行数
            /// </summary>
            public const int HeaderSize = 5;
            
            /// <summary>
            /// 实体
            /// </summary>
            public readonly string[] Entity = new string[HeaderSize];

            /// <summary>
            /// 注释
            /// </summary>
            public string Comment
            {
                get => Entity[0];
                set => Entity[0] = value;
            }

            /// <summary>
            /// 数据类型
            /// </summary>
            public string DataType
            {
                get => Entity[1];
                set => Entity[1] = value;
            }
            
            /// <summary>
            /// 标识符
            /// </summary>
            public string Identifier
            {
                get => Entity[2];
                set => Entity[2] = value;
            }
            
            /// <summary>
            /// 引用说明
            /// </summary>
            public string ReferenceInfo
            {
                get => Entity[3];
                set => Entity[3] = value;
            }
            
            /// <summary>
            /// 默认值
            /// </summary>
            public string Default
            {
                get => Entity[4];
                set => Entity[4] = value;
            }

            /// <summary>
            /// Id 字段
            /// </summary>
            public bool IsIdField => Identifier == "Id";
            
            /// <summary>
            /// 特殊字段
            /// </summary>
            public bool IsSpecialField => Identifier.StartsWith("#");

            /// <summary>
            /// 导出字段
            /// </summary>
            public bool IsExportField => Identifier.StartsWith("#Export");
        }

        /// <summary>
        /// 导入数据类
        /// </summary>
        public void ImportDataClasses(IDictionary<string, string> sources)
        {
            var headers = new Dictionary<string, IList<DataHeader>>();
            var rawData = new Dictionary<string, IList<string[]>>();

            var Ids = new Dictionary<string, (string Type, IDictionary<string, int> Map)>();

            foreach (var pair in sources)
            {
                var dataName = pair.Key;
                var data = new List<string[]>();
                // 读取原始数据
                using (var reader = new StreamReader(pair.Value, System.Text.Encoding.GetEncoding("GBK"))) {
                    string line;
                    while ((line = reader.ReadLine()) != null)
                    {
                        var cells = line.Split(',');
                        for (int i = 0, len = cells.Length; i < len; i++)
                            cells[i] = cells[i].Replace('|', ',');
                        data.Add(cells);
                    }
                }
                // 用原始数据填充表头数据并存储
                for (var i = 0; i < DataHeader.HeaderSize; i++)
                {
                    var line = data[0];
                    if (!headers.TryGetValue(dataName, out var header))
                    {
                        header = (headers[dataName] = new List<DataHeader>());
                        for ( var j = 0; j < line.Length; j++ )
                        {
                            var info = new DataHeader();
                            info.Column = j;
                            header.Add(info);
                        }
                    }
                    for ( var j = 0; j < line.Length; j++ )
                    {
                        header[j].Entity[i] = line[j];
                    }
                    data.RemoveAt(0);
                }
                // 将所有 Id 字段预先存储，以便处理引用
                if (headers.TryGetValue(dataName, out var headerList)) {
                    for (var i = 0; i < headerList.Count; i++)
                    {
                        var idHeader = headerList[i];
                        if (idHeader.IsIdField)
                        {
                            var idMap = new Dictionary<string, int>();
                            for (var id = 0; id < data.Count; id++)
                            {
                                idMap.Add(data[id][i], id);
                            }
                            Ids.Add(dataName, (idHeader.DataType, idMap));
                            break;
                        }
                    }
                }
                rawData.Add(dataName, data);
            }

            var generateDir = _GenerateDataClassDir;

            if (Directory.Exists(generateDir))
            {
                Directory.Delete(generateDir, true);
            }
            Directory.CreateDirectory(generateDir);

            foreach (var dataName in sources.Keys)
            {
                using (var o = new StreamWriter($"{generateDir}/{dataName}.cs"))
                {
                    var indent = 0;
                    
                    o.WriteLine("// **********************************************************");
                    o.WriteLine("// This file is generated code, don't edit it manually !!");
                    o.WriteLine("// **********************************************************");

                    void Echo(string text, int indentAdvance = 0)
                    {
                        for (int i = 0, len = indent + indentAdvance; i < len; i++)
                        {
                            o.Write('\t');
                        }
                        o.WriteLine(text);
                    }
                    
                    Echo("using System.Collections.Generic;");
                    Echo(string.Empty);

                    Echo("namespace Game.Data");
                    Echo("{");
                    indent += 1;
                    {
                        var fields = new List<(string Name, string Type)>();
                        Echo($"public class {dataName}");
                        Echo("{");
                        indent += 1;
                        {
                            var header = headers[dataName];
                            for (var i = 0; i < header.Count; i++)
                            {
                                var info = header[i];
                                if (info.Identifier.Length > 0 && !info.IsSpecialField)
                                {
                                    if (i != 0)
                                        Echo(string.Empty);
                                    Echo("/// <summary>");
                                    Echo($"/// { info.Comment }");
                                    Echo("/// </summary>");
                                    Echo($"public readonly { info.DataType } { info.Identifier };");
                                    fields.Add((info.Identifier, info.DataType));
                                }
                            }
                            Echo(string.Empty);
                        }
                        {
                            var sb = new StringBuilder();
                            for (var i = 0; i < fields.Count; i++)
                            {
                                var field = fields[i];
                                if (i != 0)
                                    sb.Append(", ");
                                sb.Append($"{field.Type} field{i}");
                            }
                            Echo("/// <summary>");
                            Echo($"/// { dataName } ctor");
                            Echo("/// </summary>");
                            Echo($"public {dataName}({ sb.ToString() })");
                            Echo("{");
                            indent += 1;
                            {
                                for (var i = 0; i < fields.Count; i++)
                                {
                                    Echo($"{ fields[i].Name } = field{i};");
                                }
                            }
                            indent -= 1;
                            Echo("}");
                            Echo(string.Empty);
                        }
                        {
                            Echo("/// <summary>");
                            Echo($"/// Export Id");
                            Echo("/// </summary>");
                            Echo($"public static class ExportId");
                            Echo("{");
                            indent += 1;
                            {
                                var header = headers[dataName];
                                for (var i = 0; i < header.Count; i++)
                                {
                                    var info = header[i];
                                    var data = rawData[dataName];
                                    if (info.IsExportField)
                                    {
                                        for (var id = 0; id < data.Count; id++)
                                        {
                                            var key = data[id][i];
                                            if (!string.IsNullOrEmpty(key))
                                            {
                                                Echo("/// <summary>");
                                                Echo($"/// { data[id][header.First(h => h.IsIdField).Column] } Id");
                                                Echo("/// </summary>");
                                                Echo($"public const { Ids[dataName].Type } { key } = { id };");
                                                Echo(string.Empty);
                                            }
                                        }
                                        break;
                                    }
                                }
                            }
                            indent -= 1;
                            Echo("}");
                            Echo(string.Empty);
                        }
                        {
                            Echo("/// <summary>");
                            Echo($"/// { dataName } data list");
                            Echo("/// </summary>");
                            Echo($"public static readonly IList<{ dataName }> Data = new List<{ dataName }>();");
                            Echo(string.Empty);
                        }
                        {
                            Echo("/// <summary>");
                            Echo($"/// { dataName } initializer");
                            Echo("/// </summary>");
                            Echo($"public static void Initialize()");
                            Echo("{");
                            indent += 1;
                            {
                                var sb = new StringBuilder();
                                Echo("Data.Clear();");
                                var header = headers[dataName];
                                var data = rawData[dataName];
                                for (var id = 0; id < data.Count; id++)
                                {
                                    sb.Clear();
                                    for (var i = 0; i < header.Count; i++)
                                    {
                                        var info = header[i];
                                        if (info.Identifier.Length > 0 && !info.IsSpecialField)
                                        {
                                            var value = data[id][i];

                                            if (string.IsNullOrEmpty(value))
                                                value = info.Default;
                                            
                                            if (i != 0)
                                                sb.Append(", ");
                                            if (info.IsIdField)
                                                sb.Append(id);
                                            else if (info.DataType == "string")
                                                sb.Append($"\"{ value }\"");
                                            else if (info.DataType == "bool")
                                                sb.Append(value.ToLower());
                                            else if (!string.IsNullOrEmpty(info.ReferenceInfo))
                                            {
                                                var target = value;
                                                foreach (var map in Ids[info.ReferenceInfo].Map)
                                                {
                                                    target = target.Replace(map.Key, $"{ map.Value}");
                                                }
                                                sb.Append(target);
                                            }
                                            else
                                                sb.Append(value);
                                        }
                                    }
                                    Echo($"Data.Add(new { dataName }({ sb.ToString() }));");
                                }
                            }
                            indent -= 1;
                            Echo("}");
                        }
                        indent -= 1;
                        Echo("}");
                    }
                    indent -= 1;
                    Echo("}");
                }
            }
        }

        [CustomEditor(typeof(DataImporter))]
        public class DataImporterEditor : UnityEditor.Editor
        {
            public override void OnInspectorGUI()
            {
                var importer = (DataImporter) target;
            
                DrawDefaultInspector();

                if (GUILayout.Button("Import Data..."))
                {
                    void LoadAllDataFile( string path, IDictionary<string, string> streamReaders )
                    {
                        var dirInfo = new DirectoryInfo(path);
                        foreach (var subDir in dirInfo.GetDirectories())
                        {
                            LoadAllDataFile(subDir.FullName, streamReaders);
                        }
                        foreach (var subFile in dirInfo.GetFiles())
                        {
                            var fileName = subFile.Name;
                            fileName = fileName.Remove(fileName.LastIndexOf(subFile.Extension, StringComparison.Ordinal));
                            streamReaders.Add(fileName, subFile.FullName);
                        }
                    }

                    var dataPath = EditorUtility.OpenFolderPanel("Select data path to import", "/", "csv");
                    var sources = new Dictionary<string, string>();
                    LoadAllDataFile(dataPath, sources);
                    importer.ImportDataClasses(sources);
                }
            }
        }
#endif
    }
}
